#ifndef CSV_H
#define CSV_H

#include <fstream>
#include <memory>
#include <stdexcept>
#include <string>
#include <vector>

namespace NahidaProject {
	namespace CSV {
		enum class __declspec(dllexport) Term { CRLF = -2 };
		enum class __declspec(dllexport) FieldType { DATA, ROW_END, CSV_END };
		using CSVType = std::vector<std::vector<std::string>>;

		inline auto operator==(const char c, const Term t) -> bool {
			switch (t) {
			case Term::CRLF:
				return c == '\r' || c == '\n';
			default:
				return static_cast<char>(t) == c;
			}
		}

		inline auto operator!=(const char c, const Term t) -> bool { 
			return !(c == t); 
		}

		class __declspec(dllexport) Field {
		public:
			explicit Field(FieldType t) : type(t) {

			}
			explicit Field(std::string&& str) : type(FieldType::DATA), data(std::move(str)) {

			}

			FieldType type;
			std::string data;
		};

		class __declspec(dllexport) CSV {
		private:
			enum class State { START_OF_FIELD, IN_FIELD, IN_QUOTED_FIELD, IN_ESCAPED_QUOTE, END_OF_ROW, EMPTY };
			State state = State::START_OF_FIELD;
			char quote = '"';
			char delimiter = ',';
			Term terminator = Term::CRLF;
			std::istream* input;
			static constexpr int FIELDBUF_CAP = 1024;
			static constexpr int INPUTBUF_CAP = 1024 * 128;
			std::string fieldBuffer{};
			std::vector<char> inputBuffer = std::vector<char>(INPUTBUF_CAP);
			bool endOfFile = false;
			size_t cursor = 0;
			size_t bytesRead = 0;
			std::streamoff scanPosition = 0;

		public:
			CSV(const CSV&) = delete;
			auto operator=(const CSV&) -> CSV & = delete;
			CSV(CSV&&) = default;
			auto operator=(CSV&&) -> CSV & = default;

			explicit CSV(std::istream& input) : input(&input) {
				fieldBuffer.reserve(FIELDBUF_CAP);
				if (!input.good()) {
					throw std::runtime_error("Something is wrong with input stream");
				}
			}

			auto Quote(char c) noexcept -> CSV&&;
			auto Delimiter(char c) noexcept -> CSV&&;
			auto Terminator(char c) noexcept -> CSV&&;
			auto Empty() -> bool;
			auto Position() const -> std::streamoff;
			auto NextField() -> Field;

		private:
			void HandleCRLF(const char c);
			auto TopToken() -> char*;
			void FillBuffer();

		public:
			class __declspec(dllexport) Iterator {
			public:
				using DifferenceType = std::ptrdiff_t;
				using ValueType = std::vector<std::string>;
				using Pointer = const std::vector<std::string>*;
				using Reference = const std::vector<std::string>&;
				using IteratorCategory = std::input_iterator_tag;

				explicit Iterator(CSV* p, bool end = false) : parser(p) {
					static constexpr size_t DEFAULT_ROW_CAPACITY = 50;
					if (!end) {
						row.reserve(DEFAULT_ROW_CAPACITY);
						currentRow = 0;
						next();
					}
				}

				auto operator++() -> Iterator&;
				auto operator++(int) -> Iterator;
				auto operator==(const Iterator& other) const -> bool;
				auto operator!=(const Iterator& other) const -> bool;
				auto operator*() const->Reference;
				auto operator->() const->Pointer;

			private:
				ValueType row{};
				CSV* parser;
				int currentRow = -1;
				void next();
			};

			auto begin() -> Iterator { 
				return Iterator(this); 
			};

			auto end() -> Iterator { 
				return Iterator(this, true); 
			};
		};
	}
}
#endif
